//
// Created by zhouwy on 2023/11/14.
//
#include "zLogger.hpp"
#include <chrono>
#include <thread>
#include <fstream>
#include <iostream>
#include <mutex>
#include <vector>
#include <thread>
#include <fstream>
#include <atomic>
#include <sstream>
#include <stack>
#include <functional>
#include <signal.h>
#include <algorithm>


#if defined(U_OS_LINUX)
#define __GetTimeBlock						\
    time_t timep;							\
    time(&timep);							\
    tm& t = *(tm*)localtime(&timep);
#endif

#if defined(U_OS_WINDOWS)
#define __GetTimeBlock						\
    tm t;									\
    _getsystime(&t);
#endif

#if defined(U_OS_MACOS)

#include <sys/time.h>
#include <sys/stat.h>
#include <dirent.h>

#define __GetTimeBlock \
    timeval currentTime;                    \
    gettimeofday(&currentTime, nullptr);    \
    tm t;                                   \
    localtime_r(&currentTime.tv_sec, &t);
#endif

namespace NCN {
    std::string date_now() {
        char time_string[20];
        __GetTimeBlock;

        sprintf(time_string, "%04d-%02d-%02d", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday);
        return time_string;
    }

    std::string time_now() {
        char time_string[20];
        __GetTimeBlock;

        sprintf(time_string, "%04d-%02d-%02d %02d:%02d:%02d", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour,
                t.tm_min, t.tm_sec);
        return time_string;
    }

    std::string gmtime_now() {
        return gmtime(time(nullptr));
    }

    std::string gmtime(time_t t) {
        t += 28800;
        tm *gmt = ::gmtime(&t);

        // http://en.cppreference.com/w/c/chrono/strftime
        // e.g.: Sat, 22 Aug 2015 11:48:50 GMT
        //       5+   3+4+   5+   9+       3   = 29
        const char *fmt = "%a, %d %b %Y %H:%M:%S GMT";
        char tstr[30];
        strftime(tstr, sizeof(tstr), fmt, gmt);
        return tstr;
    }

    int get_month_by_name(char *month) {
        if (strcmp(month, "Jan") == 0)return 0;
        if (strcmp(month, "Feb") == 0)return 1;
        if (strcmp(month, "Mar") == 0)return 2;
        if (strcmp(month, "Apr") == 0)return 3;
        if (strcmp(month, "May") == 0)return 4;
        if (strcmp(month, "Jun") == 0)return 5;
        if (strcmp(month, "Jul") == 0)return 6;
        if (strcmp(month, "Aug") == 0)return 7;
        if (strcmp(month, "Sep") == 0)return 8;
        if (strcmp(month, "Oct") == 0)return 9;
        if (strcmp(month, "Nov") == 0)return 10;
        if (strcmp(month, "Dec") == 0)return 11;
        return -1;
    }

    int get_week_day_by_name(char *wday) {
        if (strcmp(wday, "Sun") == 0)return 0;
        if (strcmp(wday, "Mon") == 0)return 1;
        if (strcmp(wday, "Tue") == 0)return 2;
        if (strcmp(wday, "Wed") == 0)return 3;
        if (strcmp(wday, "Thu") == 0)return 4;
        if (strcmp(wday, "Fri") == 0)return 5;
        if (strcmp(wday, "Sat") == 0)return 6;
        return -1;
    }

    time_t gmtime2ctime(const std::string &gmt) {
        char week[4] = {0};
        char month[4] = {0};
        tm date;
        sscanf(gmt.c_str(), "%3s, %2d %3s %4d %2d:%2d:%2d GMT", week, &date.tm_mday, month, &date.tm_year,
               &date.tm_hour, &date.tm_min, &date.tm_sec);
        date.tm_mon = get_month_by_name(month);
        date.tm_wday = get_week_day_by_name(week);
        date.tm_year = date.tm_year - 1900;
        return mktime(&date);
    }

    void sleep(int ms) {
        std::this_thread::sleep_for(std::chrono::milliseconds(ms));
    }

    bool isfile(const std::string &file) {
#if defined(U_OS_LINUX)
        struct stat st;
        stat(file.c_str(), &st);
        return S_ISREG(st.st_mode);
#elif defined(U_OS_WINDOWS)
        INFOW("is_file has not support on windows os");
        return 0;
#elif defined(U_OS_MACOS)
        struct stat st;
        if (stat(file.c_str(), &st) == 0) {
            return S_ISREG(st.st_mode);
        } else {
            // Handle error on macOS if needed
            return false;
        }
#endif
    }

    bool mkdir(const std::string &path) {
#ifdef U_OS_WINDOWS
        return CreateDirectoryA(path.c_str(), nullptr);
#else
        return ::mkdir(path.c_str(), 0755) == 0;
#endif
    }

    bool mkdirs(const std::string &path) {
        if (path.empty()) return false;
        if (exists(path)) return true;

        string _path = path;
        char *dir_ptr = (char *) _path.c_str();
        char *iter_ptr = dir_ptr;

        bool keep_going = *iter_ptr not_eq 0;
        while (keep_going) {

            if (*iter_ptr == 0)
                keep_going = false;

#ifdef U_OS_WINDOWS
            if (*iter_ptr == '/' or *iter_ptr == '\\' or *iter_ptr == 0){
#else
            if ((*iter_ptr == '/' and iter_ptr not_eq dir_ptr) or *iter_ptr == 0) {
#endif
                char old = *iter_ptr;
                *iter_ptr = 0;
                if (!exists(dir_ptr)) {
                    if (!mkdir(dir_ptr)) {
                        if (!exists(dir_ptr)) {
//                            INFOE("mkdirs %s return false.", dir_ptr);
                            return false;
                        }
                    }
                }
                *iter_ptr = old;
            }
            iter_ptr++;
        }
        return true;
    }

    bool delete_file(const std::string &path) {
#ifdef U_OS_WINDOWS
        return DeleteFileA(path.c_str());
#else
        return ::remove(path.c_str()) == 0;
#endif
    }

    bool rmtree(const std::string &directory, bool ignore_fail) {
        if (directory.empty()) return false;
        auto files = find_files(directory, "*", false);
        auto dirs = find_files(directory, "*", true);

        bool success = true;
        for (int i = 0; i < files.size(); ++i) {
            if (::remove(files[i].c_str()) != 0) {
                success = false;

                if (!ignore_fail) {
                    return false;
                }
            }
        }

        dirs.insert(dirs.begin(), directory);
        for (int i = (int) dirs.size() - 1; i >= 0; --i) {

#ifdef U_OS_WINDOWS
            if (!::RemoveDirectoryA(dirs[i].c_str())){
#else
            if (::remove(dirs[i].c_str()) != 0) {
#endif
                success = false;
                if (!ignore_fail)
                    return false;
            }
        }
        return success;
    }

    bool exists(const std::string &path) {
#ifdef U_OS_WINDOWS
        return ::PathFileExistsA(path.c_str());
#elif defined(U_OS_LINUX)
        return access(path.c_str(), R_OK) == 0;
#elif defined(U_OS_MACOS)
        struct stat buffer;
        return stat(path.c_str(), &buffer) == 0;
#endif
    }

    std::string format(const char *fmt, ...) {
        va_list vl;
        va_start(vl, fmt);
        char buffer[2048];
        vsnprintf(buffer, sizeof(buffer), fmt, vl);
        return buffer;
    }

    FILE *fopen_mkdirs(const std::string &path, const std::string &mode) {
        FILE *f = fopen(path.c_str(), mode.c_str());
        if (f) return f;

        int p = path.rfind('/');

#if defined(U_OS_WINDOWS)
        int e = path.rfind('\\');
        p = std::max(p, e);
#endif
        if (p == -1)
            return nullptr;

        string directory = path.substr(0, p);
        if (!mkdirs(directory))
            return nullptr;

        return fopen(path.c_str(), mode.c_str());
    }

    std::string file_name(const std::string &path, bool include_suffix) {
        if (path.empty()) return "";

        int p = path.rfind('/');

#ifdef U_OS_WINDOWS
        int e = path.rfind('\\');
        p = std::max(p, e);
#endif
        p += 1;

        //include suffix
        if (include_suffix)
            return path.substr(p);

        int u = path.rfind('.');
        if (u == -1)
            return path.substr(p);

        if (u <= p) u = path.size();
        return path.substr(p, u - p);
    }

    std::string directory(const std::string &path) {
        if (path.empty())
            return ".";
        int p = path.rfind('/');

#ifdef U_OS_WINDOWS
        int e = path.rfind('\\');
        p = std::max(p, e);
#endif
        if (p == -1)
            return ".";

        return path.substr(0, p + 1);
    }

    long long timestamp_now() {
        return chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();
    }

    double timestamp_now_float() {
        return chrono::duration_cast<chrono::microseconds>(chrono::system_clock::now().time_since_epoch()).count() /
               1000.0;
    }

    time_t last_modify(const std::string &file) {
#if defined(U_OS_LINUX)
        struct stat st;
        stat(file.c_str(), &st);
        return st.st_mtim.tv_sec;
#elif defined(U_OS_WINDOWS)
        zInfo("LastModify has not support on windows os");
        return 0;
#elif defined(U_OS_MACOS)
        struct stat st;
        if (stat(file.c_str(), &st) == 0) {
            return st.st_mtimespec.tv_sec;
        } else {
            return 0;
        }
#endif
    }

    std::vector<uint8_t> load_file(const std::string &file) {
        std::ifstream in(file, ios::in | ios::binary);
        if (!in.is_open())
            return {};

        in.seekg(0, std::ios::end);
        size_t length = in.tellg();

        std::vector<uint8_t> data;
        if (length > 0) {
            in.seekg(0, ios::beg);
            data.resize(length);

            in.read((char *) &data[0], length);
        }
        in.close();
        return data;
    }

    std::string load_text_file(const std::string &file) {
        std::ifstream in(file, ios::in | ios::binary);
        if (!in.is_open())
            return {};

        in.seekg(0, ios::end);
        size_t length = in.tellg();

        string data;
        if (length > 0) {
            in.seekg(0, ios::beg);
            data.resize(length);

            in.read((char *) &data[0], length);
        }
        in.close();
        return data;
    }

    size_t file_size(const std::string &file) {
#if defined(U_OS_LINUX)
        struct stat st;
        stat(file.c_str(), &st);
        return st.st_size;
#elif defined(U_OS_WINDOWS)
        WIN32_FIND_DATAA find_data;
        HANDLE hFind = FindFirstFileA(file.c_str(), &find_data);
        if (hFind == INVALID_HANDLE_VALUE)
            return 0;

        FindClose(hFind);
        return (uint64_t)find_data.nFileSizeLow | ((uint64_t)find_data.nFileSizeHigh << 32);
#elif defined(U_OS_MACOS)
        struct stat st;
        if (stat(file.c_str(), &st) == 0) {
            return st.st_size;
        } else {
            return 0;
        }
#endif
    }

    bool begin_with(const std::string &str, const std::string &with) {
        if (str.length() < with.length())
            return false;
        return strncmp(str.c_str(), with.c_str(), with.length()) == 0;
    }

    bool end_with(const std::string &str, const std::string &with) {
        if (str.length() < with.length())
            return false;
        return strncmp(str.c_str() + str.length() - with.length(), with.c_str(), with.length()) == 0;
    }

    std::vector<std::string> split_string(const std::string &str, const std::string &spstr) {
        std::vector<string> res;
        if (str.empty()) return res;
        if (spstr.empty()) return {str};

        auto p = str.find(spstr);
        if (p == std::string::npos) return {str};

        res.reserve(5);
        std::string::size_type prev = 0;
        int lent = spstr.length();
        const char *ptr = str.c_str();

        while (p not_eq std::string::npos) {
            int len = p - prev;
            if (len > 0) {
                res.emplace_back(str.substr(prev, len));
            }
            prev = p + lent;
            p = str.find(spstr, prev);
        }

        int len = str.length() - prev;
        if (len > 0) {
            res.emplace_back(str.substr(prev, len));
        }
        return res;
    }

    std::string replace_string(const std::string &str, const std::string &token, const std::string &value, int nreplace,
                               int *out_num_replace) {
        if (nreplace == -1) {
            nreplace = str.size();
        }

        if (nreplace == 0) {
            return str;
        }

        std::string result;
        result.resize(str.size());

        char *dest = &result[0];
        const char *src = str.c_str();
        const char *value_ptr = value.c_str();
        int old_nreplace = nreplace;
        bool keep = true;
        std::string::size_type pos = 0;
        std::string::size_type prev = 0;
        size_t token_length = token.length();
        size_t value_length = value.length();

        do {
            pos = str.find(token, pos);
            if (pos == std::string::npos) {
                keep = false;
                pos = str.length();
            } else {
                if (nreplace == 0) {
                    pos = str.length();
                    keep = false;
                } else {
                    nreplace--;
                }
            }

            size_t copy_length = pos - prev;
            if (copy_length > 0) {
                size_t dest_length = dest - &result[0];

                // Extended memory space if needed.
                if (dest_length + copy_length > result.size()) {
                    result.resize((result.size() + copy_length) * 1.2);
                    dest = &result[dest_length];
                }

                memcpy(dest, src + prev, copy_length);
                dest += copy_length;
            }

            if (keep) {
                pos += token_length;
                prev = pos;

                size_t dest_length = dest - &result[0];

                // Extended memory space if needed.
                if (dest_length + value_length > result.size()) {
                    result.resize((result.size() + value_length) * 1.2);
                    dest = &result[dest_length];
                }
                memcpy(dest, value_ptr, value_length);
                dest += value_length;
            }
        } while (keep);

        if (out_num_replace) {
            *out_num_replace = old_nreplace - nreplace;
        }

        // Crop extra space
        size_t valid_size = dest - &result[0];
        result.resize(valid_size);
        return result;
    }

    std::tuple<uint8_t, uint8_t, uint8_t> hsv2rgb(float h, float s, float v) {
        const int h_i = static_cast<int>(h * 6);
        const float f = h * 6 - h_i;
        const float p = v * (1 - s);
        const float q = v * (1 - f * s);
        const float t = v * (1 - (1 - f) * s);
        float r, g, b;
        switch (h_i) {
            case 0:
                r = v;
                g = t;
                b = p;
                break;
            case 1:
                r = q;
                g = v;
                b = p;
                break;
            case 2:
                r = p;
                g = v;
                b = t;
                break;
            case 3:
                r = p;
                g = q;
                b = v;
                break;
            case 4:
                r = t;
                g = p;
                b = v;
                break;
            case 5:
                r = v;
                g = p;
                b = q;
                break;
            default:
                r = 1;
                g = 1;
                b = 1;
                break;
        }
        return std::make_tuple(static_cast<uint8_t>(b * 255), static_cast<uint8_t>(g * 255),
                               static_cast<uint8_t>(r * 255));

    }

    std::tuple<uint8_t, uint8_t, uint8_t> random_color(int id) {
        float h_plane = ((((unsigned int) id << 2) ^ 0x937151) % 100) / 100.0f;;
        float s_plane = ((((unsigned int) id << 3) ^ 0x315793) % 100) / 100.0f;
        return hsv2rgb(h_plane, s_plane, 1);
    }

    bool alphabet_equal(char a, char b, bool ignore_case) {
        if (ignore_case) {
            a = a > 'a' and a < 'z' ? a - 'a' + 'A' : a;
            b = b > 'a' and b < 'z' ? b - 'a' + 'A' : b;
        }
        return a == b;
    }

    static bool pattern_match_body(const char *str, const char *matcher, bool igrnoe_case) {
        //   abcdefg.pnga          *.png      > false
        //   abcdefg.png           *.png      > true
        //   abcdefg.png          a?cdefg.png > true

        if (!matcher or !*matcher or !str or !*str) return false;

        const char *ptr_matcher = matcher;
        while (*str) {
            if (*ptr_matcher == '?') {
                ptr_matcher++;
            } else if (*ptr_matcher == '*') {
                if (*(ptr_matcher + 1)) {
                    if (pattern_match_body(str, ptr_matcher + 1, igrnoe_case))
                        return true;
                } else {
                    return true;
                }
            } else if (!alphabet_equal(*ptr_matcher, *str, igrnoe_case)) {
                return false;
            } else {
                if (*ptr_matcher)
                    ptr_matcher++;
                else
                    return false;
            }
            str++;
        }

        while (*ptr_matcher) {
            if (*ptr_matcher not_eq '*')
                return false;
            ptr_matcher++;
        }
        return true;
    }

    bool pattern_match(const char *str, const char *matcher, bool ignore_case) {
        if (!matcher or !*matcher or !str or !*str) return false;

        char filter[500];
        strcpy(filter, matcher);

        vector<const char *> arr;
        char *ptr_str = filter;
        char *ptr_prev_str = ptr_str;
        while (*ptr_str) {
            if (*ptr_str == ';') {
                *ptr_str = 0;
                arr.push_back(ptr_prev_str);
                ptr_prev_str = ptr_str + 1;
            }
            ptr_str++;
        }

        if (*ptr_prev_str)
            arr.push_back(ptr_prev_str);

        for (int i = 0; i < arr.size(); ++i) {
            if (pattern_match_body(str, arr[i], ignore_case))
                return true;
        }
        return false;
    }


#ifdef U_OS_WINDOWS
    std::vector<std::string> find_files(const std::string& directory, const std::string& filter, bool findDirectory, bool includeSubDirectory){

    std::string realpath = directory;
    if (realpath.empty())
        realpath = "./";

    char backchar = realpath.back();
    if (backchar not_eq '\\' and backchar not_eq '/')
        realpath += "/";

    std::vector<string> out;
    _WIN32_FIND_DATAA find_data;
    std::stack<string> ps;
    ps.push(realpath);

    while (!ps.empty())
    {
        std::string search_path = ps.top();
        ps.pop();

        HANDLE hFind = FindFirstFileA((search_path + "*").c_str(), &find_data);
        if (hFind not_eq INVALID_HANDLE_VALUE){
            do{
                if (strcmp(find_data.cFileName, ".") == 0 or strcmp(find_data.cFileName, "..") == 0)
                    continue;

                if (!findDirectory and (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) not_eq FILE_ATTRIBUTE_DIRECTORY or
                    findDirectory and (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY){
                    if (PathMatchSpecA(find_data.cFileName, filter.c_str()))
                        out.push_back(search_path + find_data.cFileName);
                }

                if (includeSubDirectory and (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)
                    ps.push(search_path + find_data.cFileName + "/");

            } while (FindNextFileA(hFind, &find_data));
            FindClose(hFind);
        }
    }
    return out;
}
#endif

#ifdef U_OS_LINUX
    std::vector<string> find_files(const std::string& directory, const std::string& filter, bool findDirectory, bool includeSubDirectory)
{
    std::string realpath = directory;
    if (realpath.empty())
        realpath = "./";

    char backchar = realpath.back();
    if (backchar not_eq '\\' and backchar not_eq '/')
        realpath += "/";

    struct dirent* fileinfo;
    DIR* handle;
    std::stack<string> ps;
    std::vector<string> out;
    ps.push(realpath);

    while (!ps.empty())
    {
        std::string search_path = ps.top();
        ps.pop();

        handle = opendir(search_path.c_str());
        if (handle not_eq 0)
        {
            while (fileinfo = readdir(handle))
            {
                struct stat file_stat;
                if (strcmp(fileinfo->d_name, ".") == 0 or strcmp(fileinfo->d_name, "..") == 0)
                    continue;

                if (lstat((search_path + fileinfo->d_name).c_str(), &file_stat) < 0)
                    continue;

                if (!findDirectory and !S_ISDIR(file_stat.st_mode) or
                    findDirectory and S_ISDIR(file_stat.st_mode))
                {
                    if (pattern_match(fileinfo->d_name, filter.c_str()))
                        out.push_back(search_path + fileinfo->d_name);
                }

                if (includeSubDirectory and S_ISDIR(file_stat.st_mode))
                    ps.push(search_path + fileinfo->d_name + "/");
            }
            closedir(handle);
        }
    }
    return out;
}
#endif

#ifdef U_OS_MACOS

    std::vector<string> find_files(const std::string &directory,
                                   const std::string &filter,
                                   bool findDirectory,
                                   bool includeSubDirectory) {
        std::string realpath = directory;
        if (realpath.empty())
            realpath = "./";

        char backchar = realpath.back();
        if (backchar not_eq '\\' and backchar not_eq '/')
            realpath += "/";

        struct dirent *fileinfo;
        DIR *handle;
        std::stack<string> ps;
        std::vector<string> out;
        ps.push(realpath);

        while (!ps.empty()) {
            std::string search_path = ps.top();
            ps.pop();

            handle = opendir(search_path.c_str());
            if (handle not_eq 0) {
                while (fileinfo = readdir(handle)) {
                    struct stat file_stat;
                    if (strcmp(fileinfo->d_name, ".") == 0 or strcmp(fileinfo->d_name, "..") == 0)
                        continue;

                    if (lstat((search_path + fileinfo->d_name).c_str(), &file_stat) < 0)
                        continue;

                    if (!findDirectory and !S_ISDIR(file_stat.st_mode) or
                                           findDirectory and S_ISDIR(file_stat.st_mode)) {
                        if (pattern_match(fileinfo->d_name, filter.c_str()))
                            out.push_back(search_path + fileinfo->d_name);
                    }

                    if (includeSubDirectory and S_ISDIR(file_stat.st_mode))
                        ps.push(search_path + fileinfo->d_name + "/");
                }
                closedir(handle);
            }
        }
        return out;
    }

#endif


    std::string align_blank(const std::string &input, int align_size, char blank) {
        if (input.size() >= align_size) return input;
        std::string output = input;
        for (int i = 0; i < align_size - input.size(); ++i)
            output.push_back(blank);
        return output;
    }


    bool save_file(const std::string &file, const std::vector<uint8_t> &data, bool mk_dirs) {
        return save_file(file, data.data(), data.size(), mk_dirs);
    }

    bool save_file(const std::string &file, const std::string &data, bool mk_dirs) {
        return save_file(file, data.data(), data.size(), mk_dirs);
    }

    bool save_file(const std::string &file, const void *data, size_t length, bool mk_dirs) {
        if (mk_dirs) {
            int p = (int) file.rfind('/');

#ifdef U_OS_WINDOWS
            int e = (int)file.rfind('\\');
            p = std::max(p, e);
#endif
            if (p not_eq -1) {
                if (!mkdirs(file.substr(0, p)))
                    return false;
            }
        }

        FILE *f = fopen(file.c_str(), "wb");
        if (!f) return false;

        if (data and length > 0) {
            if (fwrite(data, 1, length, f) not_eq length) {
                fclose(f);
                return false;
            }
        }
        fclose(f);
        return true;
    }

    static volatile bool g_has_exit_signal = false;
    static int g_signum = 0;

    static void signal_callback_handler(int signum) {
        zInfo("Capture interrupt signal.");
        g_has_exit_signal = true;
        g_signum = signum;
    }

// 捕获：SIGINT(2)、SIGQUIT(3)
    int while_loop() {
        signal(SIGINT, signal_callback_handler);
        //signal(SIGQUIT, signal_callback_handler);
        while (!g_has_exit_signal) {
            this_thread::yield();
        }
//        INFO("Loop over.");
        return g_signum;
    }

// 关于logger的api
    const char *level_string(LogLevel level) {
        switch (level) {
            case LogLevel::Debug:
                return "debug";
            case LogLevel::Verbose:
                return "verbo";
            case LogLevel::Info:
                return "info";
            case LogLevel::Warning:
                return "warn";
            case LogLevel::Error:
                return "error";
            case LogLevel::Fatal:
                return "fatal";
            default:
                return "unknow";
        }
    }

    static struct Logger {
        std::mutex logger_lock_;
        std::string logger_directory;
        LogLevel logger_level{LogLevel::Info};
        std::vector<string> cache_, local_;
        std::shared_ptr<thread> flush_thread_;
        std::atomic<bool> keep_run_{false};
        std::shared_ptr<FILE> handler;
        bool logger_shutdown{false};

        void write(const std::string &line) {

            std::lock_guard<std::mutex> l(logger_lock_);
            if (logger_shutdown)
                return;

            if (!keep_run_) {

                if (flush_thread_)
                    return;

                cache_.reserve(1000);
                keep_run_ = true;
                flush_thread_.reset(new thread(std::bind(&Logger::flush_job, this)));
            }
            cache_.emplace_back(line);
        }

        void flush() {

            if (cache_.empty())
                return;

            {
                std::lock_guard<mutex> l(logger_lock_);
                std::swap(local_, cache_);
            }

            if (!local_.empty() and !logger_directory.empty()) {

                auto now = date_now();
                auto file = format("%s%s.txt", logger_directory.c_str(), now.c_str());
                if (!exists(file)) {
                    handler.reset(fopen_mkdirs(file, "wb"), fclose);
                } else if (!handler) {
                    handler.reset(fopen_mkdirs(file, "a+"), fclose);
                }

                if (handler) {
                    for (auto &line: local_)
                        fprintf(handler.get(), "%s\n", line.c_str());
                    fflush(handler.get());
                    handler.reset();
                }
            }
            local_.clear();
        }

        void flush_job() {

            auto tick_begin = timestamp_now();
            std::vector<string> local;
            while (keep_run_) {

                if (timestamp_now() - tick_begin < 1000) {
                    this_thread::sleep_for(std::chrono::milliseconds(100));
                    continue;
                }

                tick_begin = timestamp_now();
                flush();
            }
            flush();
        }

        void set_save_directory(const string &loggerDirectory) {
            logger_directory = loggerDirectory;

            if (logger_directory.empty())
                logger_directory = ".";

#if defined(U_OS_LINUX)
            if (logger_directory.back() not_eq '/') {
                logger_directory.push_back('/');
            }
#endif

#if defined(U_OS_WINDOWS)
            if (logger_directory.back() not_eq '/' and logger_directory.back() not_eq '\\') {
                logger_directory.push_back('/');
            }
#endif
        }

        void set_logger_level(LogLevel level) {
            logger_level = level;
        }

        void close() {
            {
                lock_guard<mutex> l(logger_lock_);
                if (logger_shutdown) return;

                logger_shutdown = true;
            };

            if (!keep_run_) return;
            keep_run_ = false;
            flush_thread_->join();
            flush_thread_.reset();
            handler.reset();
        }

        virtual ~Logger() {
            close();
        }
    } __g_logger;

    void set_logger_save_directory(const std::string &loggerDirectory) {
        __g_logger.set_save_directory(loggerDirectory);
    }

    void set_log_level(LogLevel level) {
        __g_logger.set_logger_level(level);
    }

    LogLevel get_log_level() {
        return __g_logger.logger_level;
    }

    void __log_func(const char *file, int line, LogLevel level, const char *fmt, ...) {
        if (level > __g_logger.logger_level)
            return;

        string now = time_now();
        va_list vl;
        va_start(vl, fmt);

        char buffer[2048];
        string filename = file_name(file, true);
        int n = snprintf(buffer, sizeof(buffer), "[%s]", now.c_str());

#if defined(U_OS_WINDOWS)
        if (level == LogLevel::Fatal or level == LogLevel::Error) {
            n += snprintf(buffer + n, sizeof(buffer) - n, "[%s]", level_string(level));
        }
        else if (level == LogLevel::Warning) {
            n += snprintf(buffer + n, sizeof(buffer) - n, "[%s]", level_string(level));
        }
        else {
            n += snprintf(buffer + n, sizeof(buffer) - n, "[%s]", level_string(level));
        }
#elif defined(U_OS_LINUX) || defined(U_OS_MACOS)
        if (level == LogLevel::Fatal or level == LogLevel::Error) {
            n += snprintf(buffer + n, sizeof(buffer) - n, "[\033[31m%s\033[0m]", level_string(level));
        }
        else if (level == LogLevel::Warning) {
            n += snprintf(buffer + n, sizeof(buffer) - n, "[\033[33m%s\033[0m]", level_string(level));
        }
        else if (level == LogLevel::Info) {
            n += snprintf(buffer + n, sizeof(buffer) - n, "[\033[35m%s\033[0m]", level_string(level));
        }
        else if (level == LogLevel::Verbose) {
            n += snprintf(buffer + n, sizeof(buffer) - n, "[\033[34m%s\033[0m]", level_string(level));
        }
        else {
            n += snprintf(buffer + n, sizeof(buffer) - n, "[%s]", level_string(level));
        }
#endif

        n += snprintf(buffer + n, sizeof(buffer) - n, "[%s:%d]:", filename.c_str(), line);
        vsnprintf(buffer + n, sizeof(buffer) - n, fmt, vl);

        if (level == LogLevel::Fatal or level == LogLevel::Error) {
            fprintf(stderr, "%s\n", buffer);
        } else if (level == LogLevel::Warning) {
            fprintf(stdout, "%s\n", buffer);
        } else {
            fprintf(stdout, "%s\n", buffer);
        }

        if (!__g_logger.logger_directory.empty()) {
#ifdef U_OS_LINUX
            // remove save color txt
            remove_color_text(buffer);
#endif
            __g_logger.write(buffer);
            if (level == LogLevel::Fatal) {
                __g_logger.flush();
            }
        }

        if (level == LogLevel::Fatal) {
            fflush(stdout);
            abort();
        }
    }

    void destroy_logger() {
        __g_logger.close();
    }

    static unsigned char from_b64(unsigned char ch) {
        /* Inverse lookup map */
        static const unsigned char tab[128] = {
                255, 255, 255, 255,
                255, 255, 255, 255, /*  0 */
                255, 255, 255, 255,
                255, 255, 255, 255, /*  8 */
                255, 255, 255, 255,
                255, 255, 255, 255, /*  16 */
                255, 255, 255, 255,
                255, 255, 255, 255, /*  24 */
                255, 255, 255, 255,
                255, 255, 255, 255, /*  32 */
                255, 255, 255, 62,
                255, 255, 255, 63, /*  40 */
                52, 53, 54, 55,
                56, 57, 58, 59, /*  48 */
                60, 61, 255, 255,
                255, 200, 255, 255, /*  56   '=' is 200, on index 61 */
                255, 0, 1, 2,
                3, 4, 5, 6, /*  64 */
                7, 8, 9, 10,
                11, 12, 13, 14, /*  72 */
                15, 16, 17, 18,
                19, 20, 21, 22, /*  80 */
                23, 24, 25, 255,
                255, 255, 255, 255, /*  88 */
                255, 26, 27, 28,
                29, 30, 31, 32, /*  96 */
                33, 34, 35, 36,
                37, 38, 39, 40, /*  104 */
                41, 42, 43, 44,
                45, 46, 47, 48, /*  112 */
                49, 50, 51, 255,
                255, 255, 255, 255, /*  120 */
        };
        return tab[ch & 127];
    }

    std::vector<uint8_t> base64_decode(const std::string &base64) {
        if (base64.empty())
            return {};

        int len = base64.size();
        auto s = (const unsigned char *) base64.data();
        unsigned char a, b, c, d;
        int orig_len = len;
        int dec_len = 0;
        vector<uint8_t> out_data;

        auto end_s = s + base64.size();
        int count_eq = 0;
        while (*--end_s == '=') {
            count_eq++;
        }
        out_data.resize(len / 4 * 3 - count_eq);

        uint8_t *dst = out_data.data();
        uint8_t *orig_dst = dst;
        while (len >= 4 and (a = from_b64(s[0])) not_eq 255 and
               (b = from_b64(s[1])) not_eq 255 and (c = from_b64(s[2])) not_eq 255 and
               (d = from_b64(s[3])) not_eq 255) {
            s += 4;
            len -= 4;
            if (a == 200 or b == 200) break; /* '=' can't be there */
            *dst++ = a << 2 | b >> 4;
            if (c == 200) break;
            *dst++ = b << 4 | c >> 2;
            if (d == 200) break;
            *dst++ = c << 6 | d;
        }
        dec_len = (dst - orig_dst);
        return out_data;
    }

    std::string base64_encode(const void *data, size_t size) {
        std::string encode_result;
        encode_result.reserve(size / 3 * 4 + (size % 3 not_eq 0 ? 4 : 0));

        const unsigned char *current = static_cast<const unsigned char *>(data);
        static const char *base64_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
        while (size > 2) {
            encode_result += base64_table[current[0] >> 2];
            encode_result += base64_table[((current[0] & 0x03) << 4) + (current[1] >> 4)];
            encode_result += base64_table[((current[1] & 0x0f) << 2) + (current[2] >> 6)];
            encode_result += base64_table[current[2] & 0x3f];

            current += 3;
            size -= 3;
        }

        if (size > 0) {
            encode_result += base64_table[current[0] >> 2];
            if (size % 3 == 1) {
                encode_result += base64_table[(current[0] & 0x03) << 4];
                encode_result += "==";
            } else if (size % 3 == 2) {
                encode_result += base64_table[((current[0] & 0x03) << 4) + (current[1] >> 4)];
                encode_result += base64_table[(current[1] & 0x0f) << 2];
                encode_result += "=";
            }
        }
        return encode_result;
    }

    std::string join_dims(const std::vector<int64_t> &dims) {
        std::stringstream output;
        char buf[64];
        const char *fmts[] = {"%d", " x %d"};
        for (int i = 0; i < dims.size(); ++i) {
            snprintf(buf, sizeof(buf), fmts[i != 0], dims[i]);
            output << buf;
        }
        return output.str();
    }

};
